package com.zf.nospring.rabbitmq.topic;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zf.nospring.rabbitmq.utils.RabbitUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * Topic交换机。
 * Topic 类型交换机 可以让队列在绑定队列时的时候使用 路由键包含通配符！
 * <p>
 * 单词：Routing Key 一般都是有一个或多个单词组成，多个单词之间以 "." 分割，
 * 例如： “item.insert”, 注意 “item.”这种也算是两个单词【item, 空字符串】,“item..”这种也算是三个单词【item,空字符串,空字符串】
 * <p>
 * 通配符规则：通配符 # 匹配 零个或多个 单词。例如：item.# 能够匹配 "item.insert.abc"、"item.insert"、"item"、"item." 、 "item.." 、"item..." 。
 *          通配符 * 匹配恰好1个词。如 item.* 能匹配 "item.insert"、"item."，但是不能匹配 "item"，因为"item"后面没有其他单词
 * <p>
 * 当没有使用 #、* 通配符时，和direct交换机一样。
 *
 * <p>
 * 向Topic类型交换机发送消息时，如果 消息携带的路由键和绑定时配置的路由键 满足匹配规则，就把消息发送给对应的绑定队列中。
 * <p>
 * 如果某个队列绑定的路由键是： # ，那么所有发送到该交换机的消息都会被发送到该队列
 * <p>
 * 如果同一个队列绑定了多次到同一个交换机，且发送到该交换机的消息的路由键恰好全都满足该队列绑定的路由键，也只会往该队列写入一条该消息
 * <p>
 * 某条消息从交换机发送到队列时，就算某个队列在该交换机上多次绑定且多次满足路由条件也只会往该队列中发送一条该消息
 */
public class TopicProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitUtils.getDefaultConnection();
        Channel channel = connection.createChannel();
        // 声明交换机
       /*
           exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
           参数：
              exchange:交换机名称
              type:交换机类型
              durable:是否持久化
              autoDelete:自动删除
              internal：如果 true 表示该交换机只能 rabbitmq 内部使用的交换机，我们的客户端无法直接使用，所以一般false，默认是false
              arguments：参数
        */
        String exchangeName = "test_topic";
        //声明交换机，不存在就创建，存在就校验已经存在的参数和声明的参数是否一致，不一致就报错
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);
        //声明队列，不存在就创建，存在就校验已经存在的参数和声明的参数是否一致，不一致就报错
        String queue1Name = "test_topic_queue1";
        String queue2Name = "test_topic_queue2";
        channel.queueDeclare(queue1Name, true, false, false, null);
        channel.queueDeclare(queue2Name, true, false, false, null);
        //绑定队列和交换机，指定路由键，如果已存在的绑定的队列、交换机、路由键、参数完全一致则不再重复绑定，否则就创建绑定
        //绑定队列和交换机，路由键中使用通配符
        channel.queueBind(queue1Name, exchangeName, "#.error");
        channel.queueBind(queue1Name, exchangeName, "order.*");
        channel.queueBind(queue2Name, exchangeName, "*.*");

        //发送消息，hh.error 可以匹配 #.error 和 *.* ，所以会把消息发送到 test_topic_queue1 和 test_topic_queue2 队列中
        channel.basicPublish(exchangeName, "hh.error", null, "hh-err".getBytes());
        //发送消息，hh.yy.error 可以匹配 #.error ，所以会把消息发送到 test_topic_queue1 队列中
        channel.basicPublish(exchangeName, "hh.yy.error", null, "yy-err".getBytes());
        //发送消息，order.oo 可以匹配 order.* 、#.error 和 *.*，所以会把消息发送到 test_topic_queue1 和 test_topic_queue2 队列中
        channel.basicPublish(exchangeName, "order.error", null, "oo".getBytes());

        // "order." 点后面空字符串也被当成一个单词了，所以可以匹配 order.* 和 *.*，所以会把消息发送到 test_topic_queue1 和 test_topic_queue2 队列中。
        // 但是 "order" 这种单词只能匹配 "#"、"order.#"、"*"，不能匹配 "order.*"
        channel.basicPublish(exchangeName, "order.", null, "点".getBytes());

        channel.close();
        connection.close();

    }
}
